package com.fitso.wicket;

import org.apache.log4j.Logger;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.Request;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Response;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.Session;
import org.apache.wicket.authorization.Action;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.WebRequestCycleProcessor;
import org.apache.wicket.request.IRequestCycleProcessor;
import org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;

import com.fitso.wicket.config.FitsoApplicationContext;
import com.fitso.wicket.config.SecureComponent;
import com.fitso.wicket.page.index.Index;
import com.fitso.wicket.page.landing.Landing;

/**
 * Main Fitso application class. This does the initial startup and handles
 * different life cycle stages of the application.
 * 
 * @author timothystorm
 */
public class FitsoApplication extends WebApplication {
	private static final Logger logger = Logger.getLogger(FitsoApplication.class);

	/**
	 * Initialize the system
	 */
	@Override
	protected void init() {
		super.init();
		logger.info("init FitsoApplication starting...");

		logger.info("initializing DI...");
		addComponentInstantiationListener(new SpringComponentInjector(this));

		logger.info("initializing sub systems...");
		mountUrls();
		setupSecurity();

		logger.info("init FitsoApplication finished.");
	}

	/**
	 * Returns the global home page for the Fitso website
	 */
	@Override
	public Class<? extends Page> getHomePage() {
		logger.debug("getting home page");
		
		if(getSession().isSignedIn()){
			return Landing.class;
		}
		return Index.class;
	}

	/**
	 * Returns back a Fitso centric {@link Session}
	 */
	@Override
	public Session newSession(Request request, Response response) {
		logger.debug("creating a new session");
		return new FitsoSession(request);
	}

	/**
	 * Mounts the urls that will be used throughout the site
	 */
	private void mountUrls() {
		logger.info("mounting URLs starting...");
		for (IRequestTargetUrlCodingStrategy s : FitsoApplicationContext.getUrlCodingStragies()) {
			mount(s);
		}
		logger.info("mounting URLs finished.");
	}

	/**
	 * Setup the security rules for pages and/or components
	 */
	private void setupSecurity() {
		logger.info("setup security starting...");

		getSecuritySettings().setAuthorizationStrategy(new IAuthorizationStrategy() {

			public boolean isActionAuthorized(Component component, Action action) {
				return true;
			}

			public <T extends Component> boolean isInstantiationAuthorized(Class<T> component) {
				if (isSecure(component)) {
					if (getSession().isSignedIn()) {
						return true;
					}
					throw new RestartResponseAtInterceptPageException(getHomePage());
				}
				return true;
			}
		});

		logger.info("setup security finished.");
	}

	/**
	 * Determines if a component should be treated as secure by checking if it
	 * is annotated with the {@link SecureComponent}
	 * 
	 * @param <T>
	 * @param componentClass
	 * @return true if the provided component is marked as {@link SecureComponent}
	 */
	private <T extends Component> boolean isSecure(Class<T> componentClass) {
		logger.debug("checking if component is secure");
		
		return componentClass.isAnnotationPresent(SecureComponent.class);
	}

	/**
	 * Utility method for getting the user's {@link FitsoSession}
	 * 
	 * @return @{link FitsoSession} for calling thread
	 */
	private FitsoSession getSession() {
		logger.debug("fetching the session");
		
		return (FitsoSession) Session.get();
	}

	/**
	 * new implementation that handles page expires
	 */
	@Override
	protected IRequestCycleProcessor newRequestCycleProcessor() {
		logger.info("creating a new request cycle processor");

		return new WebRequestCycleProcessor() {
			@Override
			protected Page onRuntimeException(Page page, RuntimeException e) {
				if (e instanceof PageExpiredException) {
					logger.warn("page expired: " + page);
					return new Index();
				}
				return super.onRuntimeException(page, e);
			}
		};
	}

	/**
	 * new implementation that handles request logging
	 */
	@Override
	public RequestCycle newRequestCycle(Request request, Response response) {
		logger.info("creating a new request cycle");
		
		return new LoggingWebRequestCycle(this, (WebRequest) request, response);
	}
}
